Using textures

Using textures in OpenGL involves a few steps:

Texture upload

Textures are uploaded from CPU memory to GPU memory. OpenGL has no understanding of image formats, so you will need to handle decoding and loading the image some other way. For us, SFML will load images. Texture objects have the standard OpenGL object syntax:
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
The GL_TEXTURE_2D tells OpenGL how we will use the texture. Almost all texture commands will require this setting.

glTexImage2D(target, level, internalFormat, width, height, border, format, type, data)

Texture parameters

There are many parameters that can be set when using textures. We will only consider two: filtering and wrapping. Filtering controls how minification and magnification are handled. OpenGL supports nearest neighbor (low quality, fast), bilinear interpolation (better, slower), and trilinear interpolation (still better, still slow). More advanced filters can be used, but we will not consider them. Filters can be set as:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
There are mipmap options (i.e. GL_LINEAR_MIPMAP_LINEAR) for the minification filter. If you use these mipmap filters, you must create the mipmaps for your texture. This can be done after the texture is uploaded by issuing:
glGenerateMipmap(GL_TEXTURE_2D);

You might also need to consider how the texture should wrap when queries are outside the [0,1] range defined for s,t. This is especially important if you need your texture to tile over a large surface. This is controlled with wrapping parameters. You can set the texture to clamp s,t at the edge and return the edge color or cause the texture to repeat by warping s,t back to [0,1] or even to repeatedly mirror the texture at the edge. There are several more options; see the documentation for details.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

Model texture mapping

When rendering, we must be able to map a location on the model surface to samples in the texture. This is often done with UV texture coordinates. For each vertex uploaded to the GPU, a texture coordinate attribute is also uploaded. These texture coordinates are the s,t values in texture space. So, a set of vertex attributes could be:
pos = {[x,y,z] [x,y,z] [x,y,z] ... }
color = {[r,g,b] [r,g,b] [r,g,b] ... }
texcoord = {[u,v] [u,v] [u,v] ... }

Activate texture hardware

Modern GPUs have special units for quickly querying texture data called texture units. The texture units are responsible for filtering and caching texture lookups. In OpenGL, we need to activate a texture unit, pick a texture to use, and tell the shader which unit we activated.
glActiveTexture(GL_TEXTURE0+unitId);   //activate a texture unit
glBindTexture(GL_TEXTURE_2D, texture); //enable a texture object
glUniform1i(inputSlot, unitId);        //let the shader know which unit to use

Shaders and textures

The host CPU must let the shader know what texture unit is active. This is done by uploading the integer id of the unit. However, the shader doesn't use the integer as an integer; instead, it uses it as a sampler. Samplers are just another name for what the texture unit will do: it will sample the texture. The sampler type needs to match the texture type used for the texture object: sampler2D for GL_TEXTURE_2D, sampler2DRect for GL_RECTANGLE, etc. The sampler can be used with the GLSL texture function to query texture values.
uniform sampler2D texUnitId;
in vec2 texCoord;
out vec4 fragColor;

main() {
	fragColor = texture(texUnitId, texCoord);
}